// ---------------------------------------------------------------------
//
// Copyright (C) 2017 - 2019 by the deal.II authors
//
// This file is part of the deal.II library.
//
// The deal.II library is free software; you can use it, redistribute
// it, and/or modify it under the terms of the GNU Lesser General
// Public License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// The full text of the license can be found in the file LICENSE.md at
// the top level directory of deal.II.
//
// ---------------------------------------------------------------------
#include <deal.II/base/bounding_box.h>

DEAL_II_NAMESPACE_OPEN

template <int spacedim, typename Number>
bool
BoundingBox<spacedim, Number>::point_inside(
  const Point<spacedim, Number> &p) const
{
  for (unsigned int i = 0; i < spacedim; ++i)
    {
      // Bottom left-top right convention: the point is outside if it's smaller
      // than the first or bigger than the second boundary point The bounding
      // box is defined as a closed set
      if (std::numeric_limits<Number>::epsilon() *
              (std::abs(this->boundary_points.first[i] + p[i])) <
            this->boundary_points.first[i] - p[i] ||
          std::numeric_limits<Number>::epsilon() *
              (std::abs(this->boundary_points.second[i] + p[i])) <
            p[i] - this->boundary_points.second[i])
        return false;
    }
  return true;
}

template <int spacedim, typename Number>
void
BoundingBox<spacedim, Number>::merge_with(
  const BoundingBox<spacedim, Number> &other_bbox)
{
  for (unsigned int i = 0; i < spacedim; ++i)
    {
      this->boundary_points.first[i] =
        std::min(this->boundary_points.first[i],
                 other_bbox.boundary_points.first[i]);
      this->boundary_points.second[i] =
        std::max(this->boundary_points.second[i],
                 other_bbox.boundary_points.second[i]);
    }
}

template <int spacedim, typename Number>
NeighborType
BoundingBox<spacedim, Number>::get_neighbor_type(
  const BoundingBox<spacedim, Number> &other_bbox) const
{
  if (spacedim == 1)
    {
      // In dimension 1 if the two bounding box are neighbors
      // we can merge them
      if (this->point_inside(other_bbox.boundary_points.first) ||
          this->point_inside(other_bbox.boundary_points.second))
        return NeighborType::mergeable_neighbors;
      return NeighborType::not_neighbors;
    }
  else
    {
      std::vector<Point<spacedim, Number>> bbox1;
      bbox1.push_back(this->get_boundary_points().first);
      bbox1.push_back(this->get_boundary_points().second);
      std::vector<Point<spacedim, Number>> bbox2;
      bbox2.push_back(other_bbox.get_boundary_points().first);
      bbox2.push_back(other_bbox.get_boundary_points().second);

      // Step 1: testing if the boxes are close enough to intersect
      for (unsigned int d = 0; d < spacedim; ++d)
        if (bbox1[0][d] * (1 - std::numeric_limits<Number>::epsilon()) >
              bbox2[1][d] ||
            bbox2[0][d] * (1 - std::numeric_limits<Number>::epsilon()) >
              bbox1[1][d])
          return NeighborType::not_neighbors;

      // The boxes intersect: we need to understand now how they intersect.
      // We begin by computing the intersection:
      std::vector<double> intersect_bbox_min;
      std::vector<double> intersect_bbox_max;
      for (unsigned int d = 0; d < spacedim; ++d)
        {
          intersect_bbox_min.push_back(std::max(bbox1[0][d], bbox2[0][d]));
          intersect_bbox_max.push_back(std::min(bbox1[1][d], bbox2[1][d]));
        }

      // Finding the intersection's dimension

      unsigned int intersect_dim = spacedim;
      for (unsigned int d = 0; d < spacedim; ++d)
        if (std::abs(intersect_bbox_min[d] - intersect_bbox_max[d]) <=
            std::numeric_limits<Number>::epsilon() *
              (std::abs(intersect_bbox_min[d]) +
               std::abs(intersect_bbox_max[d])))
          --intersect_dim;

      if (intersect_dim == 0 || intersect_dim + 2 == spacedim)
        return NeighborType::simple_neighbors;

      // Checking the two mergeable cases: first if the boxes are aligned so
      // that they can be merged
      unsigned int not_align_1 = 0, not_align_2 = 0;
      bool         same_direction = true;
      for (unsigned int d = 0; d < spacedim; ++d)
        {
          if (std::abs(bbox2[0][d] - bbox1[0][d]) >
              std::numeric_limits<double>::epsilon() *
                (std::abs(bbox2[0][d]) + std::abs(bbox1[0][d])))
            ++not_align_1;
          if (std::abs(bbox1[1][d] - bbox2[1][d]) >
              std::numeric_limits<double>::epsilon() *
                (std::abs(bbox1[1][d]) + std::abs(bbox2[1][d])))
            ++not_align_2;
          if (not_align_1 != not_align_2)
            {
              same_direction = false;
              break;
            }
        }

      if (not_align_1 <= 1 && not_align_2 <= 1 && same_direction)
        return NeighborType::mergeable_neighbors;

      // Second: one box is contained/equal to the other
      if ((this->point_inside(bbox2[0]) && this->point_inside(bbox2[1])) ||
          (other_bbox.point_inside(bbox1[0]) &&
           other_bbox.point_inside(bbox1[1])))
        return NeighborType::mergeable_neighbors;

      // Degenerate and mergeable cases have been found, it remains:
      return NeighborType::attached_neighbors;
    }
}



template <int spacedim, typename Number>
std::pair<Point<spacedim, Number>, Point<spacedim, Number>> &
BoundingBox<spacedim, Number>::get_boundary_points()
{
  return this->boundary_points;
}



template <int spacedim, typename Number>
const std::pair<Point<spacedim, Number>, Point<spacedim, Number>> &
BoundingBox<spacedim, Number>::get_boundary_points() const
{
  return this->boundary_points;
}



template <int spacedim, typename Number>
double
BoundingBox<spacedim, Number>::volume() const
{
  double vol = 1.0;
  for (unsigned int i = 0; i < spacedim; ++i)
    vol *= (this->boundary_points.second[i] - this->boundary_points.first[i]);
  return vol;
}

#include "bounding_box.inst"
DEAL_II_NAMESPACE_CLOSE
